﻿B4J=true
Group=Default Group\认证帮助类
ModulesStructureVersion=1
Type=StaticCode
Version=10.3
@EndOfDesignText@

'' 模块全局变量
Sub Process_Globals
	Dim SECRET_KEY As String = "BB3353B2639CCBBF4D3E36C0E9B3E0ED" ' 密钥-自行修改即可
	Dim ALGORITHM As String = "HMACSHA256" ' 用于签名的哈希算法。
	Dim ACCESS_TOKEN_EXPIRY As Long = 1000 * 60 * 60 * 6 ' 访问令牌有效期：6小时
	Dim REFRESH_TOKEN_EXPIRY As Long = 1000 * 60 * 60 * 24 * 7 ' 刷新令牌有效期：7天
End Sub

' 为给定的用户名创建一个JWT（JSON Web Token）。
Public Sub CreateJWT(username As String) As String
    Return CreateJWTWithExpiry(username, ACCESS_TOKEN_EXPIRY)
End Sub

' 创建一个刷新令牌（有效期更长）
Public Sub CreateRefreshToken(username As String) As String
    Return CreateJWTWithExpiry(username, REFRESH_TOKEN_EXPIRY)
End Sub

' 内部方法：创建具有指定过期时间的JWT
Private Sub CreateJWTWithExpiry(username As String, expiryMillis As Long) As String
    Dim header As Map  ' JWT头部（包含算法和令牌类型）
    header.Initialize
    header.Put("alg", "HS256") ' 用于签名的算法（HMAC-SHA256）
    header.Put("typ", "JWT") ' 令牌类型（JWT）
    header.Put("token_type", IIf(expiryMillis = ACCESS_TOKEN_EXPIRY, "access", "refresh")) ' 区分令牌类型

    Dim payload As Map ' JWT载荷（包含用户信息和其他声明）
    payload.Initialize
    payload.Put("sub", username) ' 主题（通常是用户名）
    payload.Put("iat", DateTime.Now) ' 签发时间（创建令牌的时间戳）
    payload.Put("exp", DateTime.Now + expiryMillis) ' 过期时间

    Dim headerBase64 As String = B64_Encode(JsonStringify(header)) ' Base64编码头部
    Dim payloadBase64 As String = B64_Encode(JsonStringify(payload)) ' Base64编码载荷
    Dim signature As String = HMAC_SHA256(headerBase64 & "." & payloadBase64, SECRET_KEY) ' 计算签名

    Return headerBase64 & "." & payloadBase64 & "." & signature ' 返回完整的JWT（头部.载荷.签名）
End Sub

' 刷新JWT：使用有效的刷新令牌获取新的访问令牌
Public Sub RefreshJWT(refreshToken As String) As String
    Try
        ' 首先验证刷新令牌是否有效
        If Not(VerifyJWT(refreshToken)) Then
			LogHelper.WirteLog("刷新令牌无效，无法刷新")
            Return ""
        End If
        
        ' 解析刷新令牌获取用户名
        Dim parts() As String = Regex.Split("\.", refreshToken)
        Dim payloadJson As String = B64_Decode(parts(1))
        Dim parser As JSONParser
        parser.Initialize(payloadJson)
        Dim payloadMap As Map = parser.NextObject
        
        ' 检查令牌类型是否为刷新令牌
        Dim headerJson As String = B64_Decode(parts(0))
        parser.Initialize(headerJson)
        Dim headerMap As Map = parser.NextObject
        If headerMap.Get("token_type") <> "refresh" Then
			LogHelper.WirteLog("不是刷新令牌，无法用于刷新")
            Return ""
        End If
        
        ' 使用相同的用户名创建新的访问令牌
        Dim username As String = payloadMap.Get("sub")
        Return CreateJWT(username)
        
    Catch
		LogHelper.WirteLog("刷新令牌错误: " & LastException.Message)
        Return ""
    End Try
End Sub

' 从JWT中获取用户名
Public Sub GetUsernameFromJWT(token As String) As String
	Dim tokenData As Map = ParseJWT(token)
	If tokenData.IsInitialized And tokenData.ContainsKey("payload") Then
		Dim payload As Map = tokenData.Get("payload")
		If payload.ContainsKey("sub") Then
			Return payload.Get("sub")
		End If
	End If
	Return ""
End Sub

' 从JWT中获取过期时间
Public Sub GetExpiryFromJWT(token As String) As Long
	Dim tokenData As Map = ParseJWT(token)
	If tokenData.IsInitialized And tokenData.ContainsKey("payload") Then
		Dim payload As Map = tokenData.Get("payload")
		If payload.ContainsKey("exp") Then
			Return payload.Get("exp")
		End If
	End If
	Return 0
End Sub


' 验证JWT
Public Sub VerifyJWT(token As String) As Boolean
    Try
        Dim parts() As String = Regex.Split("\.", token) ' 将令牌分割为三部分
        If parts.Length <> 3 Then ' 检查令牌格式是否正确
			LogHelper.WirteLog("无效的令牌格式")
            Return False
        End If

        Dim headerBase64 As String = parts(0) ' 提取头部
        Dim payloadBase64 As String = parts(1) ' 提取载荷
        Dim signatureReceived As String = parts(2) ' 提取收到的签名

        Dim signatureCalculated As String = HMAC_SHA256(headerBase64 & "." & payloadBase64, SECRET_KEY) ' 计算预期的签名

        If signatureCalculated <> signatureReceived Then ' 比较计算的签名和收到的签名
			LogHelper.WirteLog("签名不匹配")
            Return False
        End If

        ' 可选：验证过期时间
        Dim payloadJson As String = B64_Decode(payloadBase64) ' 将载荷从Base64解码为JSON
        Dim parser As JSONParser
        parser.Initialize(payloadJson)
        Dim payloadMap As Map = parser.NextObject ' 解析JSON载荷

        If payloadMap.ContainsKey("exp") Then ' 检查载荷是否包含过期时间
            Dim expiryTime As Long = payloadMap.Get("exp") ' 获取过期时间
            If DateTime.Now > expiryTime Then ' 检查令牌是否已过期
				LogHelper.WirteLog("令牌已过期")
                Return False
            End If
        End If

		LogHelper.WirteLog("令牌有效") ' 令牌有效
        Return True

    Catch ' 处理验证过程中的任何错误
		LogHelper.WirteLog("验证令牌错误: " & LastException.Message)
        Return False
    End Try
End Sub

' 解析JWT并返回头部和载荷信息（不验证签名）
' 返回的Map包含两个键: "header" 和 "payload"，对应的值都是Map对象
Public Sub ParseJWT(token As String) As Map
	Try
		Dim result As Map
		result.Initialize
        
		Dim parts() As String = Regex.Split("\.", token) ' 将令牌分割为三部分
		If parts.Length <> 3 Then ' 检查令牌格式是否正确
			Log("无效的令牌格式，无法解析")
			Return Null
		End If

		Dim headerBase64 As String = parts(0) ' 提取头部
		Dim payloadBase64 As String = parts(1) ' 提取载荷
        
		' 解析头部
		Dim headerJson As String = B64_Decode(headerBase64)
		Dim parser As JSONParser
		parser.Initialize(headerJson)
		Dim headerMap As Map = parser.NextObject
		result.Put("header", headerMap)
        
		' 解析载荷
		Dim payloadJson As String = B64_Decode(payloadBase64)
		parser.Initialize(payloadJson)
		Dim payloadMap As Map = parser.NextObject
		result.Put("payload", payloadMap)
        
		' 添加令牌的基本信息
		result.Put("token", token)
		result.Put("valid_format", True)
        
		Return result
        
	Catch ' 处理解析过程中的任何错误
		Log("解析令牌错误: " & LastException.Message)
		Return Null
	End Try
End Sub

' 将Map转换为JSON字符串
Private Sub JsonStringify(map As Map) As String
    Dim j As JSONGenerator
    Dim json As String
    j.Initialize(map)
    json = j.ToString
    Return json
End Sub

' 将Base64编码的字符串解码为UTF8字符串
Private Sub B64_Decode(text As String) As String
    Dim Base64 As StringUtils
    Try
        Dim jsondataBytes() As Byte ' 存储解码后的字节
        jsondataBytes = Base64.DecodeBase64(text) ' 将Base64解码为字节
        Return BytesToString(jsondataBytes, 0, jsondataBytes.Length, "UTF8") ' 将字节转换为UTF8字符串
    Catch ' 处理Base64解码错误
		LogHelper.WirteLog("Base64解码错误: " & LastException.Message)
        Return "" ' 或根据需要处理错误
    End Try
End Sub

' 使用UTF8编码将字符串编码为Base64
Private Sub B64_Encode(text As String) As String
    Dim Base64 As StringUtils
    Return Base64.EncodeBase64(text.GetBytes("UTF8")) ' 使用UTF8编码将字符串编码为Base64
End Sub

' 计算HMAC-SHA256签名
Private Sub HMAC_SHA256(data As String, key As String) As String
    Dim hmac As Mac
    Dim KG As KeyGenerator

    KG.Initialize(ALGORITHM) ' 使用指定算法初始化KeyGenerator
    KG.KeyFromBytes(key.GetBytes("UTF8")) ' 使用UTF8编码从密钥创建密钥

    hmac.Initialise(ALGORITHM, KG.Key) ' 使用算法和密钥初始化Mac对象
    hmac.Update(data.GetBytes("UTF8")) ' 使用UTF8编码更新要签名的数据
    Dim Base64 As StringUtils
    Return Base64.EncodeBase64(hmac.Sign) ' 计算签名并编码为Base64
End Sub
